<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>cannon.js mouse pick demo</title>
        <style>
            html, body {
                width: 100%;
                height: 100%;
                margin:0;
                padding:0;
                overflow: hidden;
            }
        </style>
    </head>
    <body>
        <script src="../libs/Three.js"></script>
        <script src="../libs/Detector.js"></script>
        <script src="../build/cannon.js"></script>

        <script>

        var world;
        var dt = 1 / 60;

        var constraintDown = false;
        var camera, scene, renderer, gplane=false, clickMarker=false;
        var geometry, material, mesh;
        var controls,time = Date.now();

        var jointBody, constrainedBody, mouseConstraint;

        var N = 1;

        var container, camera, scene, renderer, projector;

        // To be synced
        var meshes=[], bodies=[];

        // Initialize Three.js
        if ( ! Detector.webgl ) Detector.addGetWebGLMessage();

        initCannon();
        init();
        animate();

        function init() {

            projector = new THREE.Projector();

            container = document.createElement( 'div' );
            document.body.appendChild( container );

            // scene
            scene = new THREE.Scene();
            scene.fog = new THREE.Fog( 0x000000, 500, 10000 );

            // camera
            camera = new THREE.PerspectiveCamera( 30, window.innerWidth / window.innerHeight, 0.5, 10000 );
            camera.position.set(10, 2, 0);
            camera.quaternion.setFromAxisAngle(new THREE.Vector3(0,1,0), Math.PI/2);
            scene.add(camera);

            // lights
            var light, materials;
            scene.add( new THREE.AmbientLight( 0x666666 ) );

            light = new THREE.DirectionalLight( 0xffffff, 1.75 );
            var d = 20;

            light.position.set( d, d, d );

            light.castShadow = true;
            //light.shadowCameraVisible = true;

            light.shadowMapWidth = 1024;
            light.shadowMapHeight = 1024;

            light.shadowCameraLeft = -d;
            light.shadowCameraRight = d;
            light.shadowCameraTop = d;
            light.shadowCameraBottom = -d;

            light.shadowCameraFar = 3*d;
            light.shadowCameraNear = d;
            light.shadowDarkness = 0.5;

            scene.add( light );

            // floor
            geometry = new THREE.PlaneGeometry( 100, 100, 1, 1 );
            //geometry.applyMatrix( new THREE.Matrix4().makeRotationX( -Math.PI / 2 ) );
            material = new THREE.MeshLambertMaterial( { color: 0x777777 } );
            markerMaterial = new THREE.MeshLambertMaterial( { color: 0xff0000 } );
            //THREE.ColorUtils.adjustHSV( material.color, 0, 0, 0.9 );
            mesh = new THREE.Mesh( geometry, material );
            mesh.castShadow = true;
            mesh.quaternion.setFromAxisAngle(new THREE.Vector3(1,0,0), -Math.PI / 2);
            mesh.receiveShadow = true;
            scene.add(mesh);

            // cubes
            var cubeGeo = new THREE.BoxGeometry( 1, 1, 1, 10, 10 );
            var cubeMaterial = new THREE.MeshPhongMaterial( { color: 0x888888 } );
            for(var i=0; i<N; i++){
                cubeMesh = new THREE.Mesh(cubeGeo, cubeMaterial);
                cubeMesh.castShadow = true;
                meshes.push(cubeMesh);
                scene.add(cubeMesh);
            }

            renderer = new THREE.WebGLRenderer( { antialias: true } );
            renderer.setSize( window.innerWidth, window.innerHeight );
            renderer.setClearColor( scene.fog.color );

            container.appendChild( renderer.domElement );

            renderer.gammaInput = true;
            renderer.gammaOutput = true;
            renderer.shadowMapEnabled = true;

            window.addEventListener( 'resize', onWindowResize, false );

            window.addEventListener("mousemove", onMouseMove, false );
            window.addEventListener("mousedown", onMouseDown, false );
            window.addEventListener("mouseup", onMouseUp, false );
        }

        function setClickMarker(x,y,z) {
            if(!clickMarker){
                var shape = new THREE.SphereGeometry(0.2, 8, 8);
                clickMarker = new THREE.Mesh(shape, markerMaterial);
                scene.add(clickMarker);
            }
            clickMarker.visible = true;
            clickMarker.position.set(x,y,z);
        }

        function removeClickMarker(){
          clickMarker.visible = false;
        }

        function onMouseMove(e){
            // Move and project on the plane
            if (gplane && mouseConstraint) {
                var pos = projectOntoPlane(e.clientX,e.clientY,gplane,camera);
                if(pos){
                    setClickMarker(pos.x,pos.y,pos.z,scene);
                    moveJointToPoint(pos.x,pos.y,pos.z);
                }
            }
        }

        function onMouseDown(e){
            // Find mesh from a ray
            var entity = findNearestIntersectingObject(e.clientX,e.clientY,camera,meshes);
            var pos = entity.point;
            if(pos && entity.object.geometry instanceof THREE.BoxGeometry){
                constraintDown = true;
                // Set marker on contact point
                setClickMarker(pos.x,pos.y,pos.z,scene);

                // Set the movement plane
                setScreenPerpCenter(pos,camera);

                var idx = meshes.indexOf(entity.object);
                if(idx !== -1){
                    addMouseConstraint(pos.x,pos.y,pos.z,bodies[idx]);
                }
            }
        }

        // This function creates a virtual movement plane for the mouseJoint to move in
        function setScreenPerpCenter(point, camera) {
            // If it does not exist, create a new one
            if(!gplane) {
              var planeGeo = new THREE.PlaneGeometry(100,100);
              var plane = gplane = new THREE.Mesh(planeGeo,material);
              plane.visible = false; // Hide it..
              scene.add(gplane);
            }

            // Center at mouse position
            gplane.position.copy(point);

            // Make it face toward the camera
            gplane.quaternion.copy(camera.quaternion);
        }

        function onMouseUp(e) {
          constraintDown = false;
          // remove the marker
          removeClickMarker();

          // Send the remove mouse joint to server
          removeJointConstraint();
        }

        var lastx,lasty,last;
        function projectOntoPlane(screenX,screenY,thePlane,camera) {
            var x = screenX;
            var y = screenY;
            var now = new Date().getTime();
            // project mouse to that plane
            var hit = findNearestIntersectingObject(screenX,screenY,camera,[thePlane]);
            lastx = x;
            lasty = y;
            last = now;
            if(hit)
                return hit.point;
            return false;
        }
        function findNearestIntersectingObject(clientX,clientY,camera,objects) {
            // Get the picking ray from the point
            var raycaster = getRayCasterFromScreenCoord(clientX, clientY, camera, projector);

            // Find the closest intersecting object
            // Now, cast the ray all render objects in the scene to see if they collide. Take the closest one.
            var hits = raycaster.intersectObjects(objects);
            var closest = false;
            if (hits.length > 0) {
                closest = hits[0];
            }

            return closest;
        }

        // Function that returns a raycaster to use to find intersecting objects
        // in a scene given screen pos and a camera, and a projector
        function getRayCasterFromScreenCoord (screenX, screenY, camera, projector) {
            var mouse3D = new THREE.Vector3();
            // Get 3D point form the client x y
            mouse3D.x = (screenX / window.innerWidth) * 2 - 1;
            mouse3D.y = -(screenY / window.innerHeight) * 2 + 1;
            mouse3D.z = 0.5;
            return projector.pickingRay(mouse3D, camera);
        }

        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            //controls.handleResize();
            renderer.setSize( window.innerWidth, window.innerHeight );
        }

        function animate() {
            requestAnimationFrame( animate );
            //controls.update();
            updatePhysics();
            render();
        }

        function updatePhysics(){
            world.step(dt);
            for(var i=0; i !== meshes.length; i++){
                meshes[i].position.copy(bodies[i].position);
                meshes[i].quaternion.copy(bodies[i].quaternion);
            }
        }

        function render() {
            renderer.render(scene, camera);
        }

        function initCannon(){
            // Setup our world
            world = new CANNON.World();
            world.quatNormalizeSkip = 0;
            world.quatNormalizeFast = false;

            world.gravity.set(0,-10,0);
            world.broadphase = new CANNON.NaiveBroadphase();

            // Create boxes
            var mass = 5, radius = 1.3;
            boxShape = new CANNON.Box(new CANNON.Vec3(0.5,0.5,0.5));
            for(var i=0; i<N; i++){
                boxBody = new CANNON.Body({ mass: mass });
                boxBody.addShape(boxShape);
                boxBody.position.set(0,5,0);
                world.addBody(boxBody);
                bodies.push(boxBody);
            }

            // Create a plane
            var groundShape = new CANNON.Plane();
            var groundBody = new CANNON.Body({ mass: 0 });
            groundBody.addShape(groundShape);
            groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1,0,0),-Math.PI/2);
            world.addBody(groundBody);

            // Joint body
            var shape = new CANNON.Sphere(0.1);
            jointBody = new CANNON.Body({ mass: 0 });
            jointBody.addShape(shape);
            jointBody.collisionFilterGroup = 0;
            jointBody.collisionFilterMask = 0;
            world.addBody(jointBody)
        }

        function addMouseConstraint(x,y,z,body) {
          // The cannon body constrained by the mouse joint
          constrainedBody = body;

          // Vector to the clicked point, relative to the body
          var v1 = new CANNON.Vec3(x,y,z).vsub(constrainedBody.position);

          // Apply anti-quaternion to vector to tranform it into the local body coordinate system
          var antiRot = constrainedBody.quaternion.inverse();
          pivot = antiRot.vmult(v1); // pivot is not in local body coordinates

          // Move the cannon click marker particle to the click position
          jointBody.position.set(x,y,z);

          // Create a new constraint
          // The pivot for the jointBody is zero
          mouseConstraint = new CANNON.PointToPointConstraint(constrainedBody, pivot, jointBody, new CANNON.Vec3(0,0,0));

          // Add the constriant to world
          world.addConstraint(mouseConstraint);
        }

        // This functions moves the transparent joint body to a new postion in space
        function moveJointToPoint(x,y,z) {
            // Move the joint body to a new position
            jointBody.position.set(x,y,z);
            mouseConstraint.update();
        }

        function removeJointConstraint(){
          // Remove constriant from world
          world.removeConstraint(mouseConstraint);
          mouseConstraint = false;
        }

        </script>
    </body>
</html>
